package top.codedance.iotp.client.plugins.sync.opc;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import top.codedance.iotp.client.ClientApplication;
import top.codedance.iotp.client.net.listenner.PluginService;
import top.codedance.iotp.common.entity.PluginResultEntity;
import top.codedance.iotp.common.util.SystemUtil;
import com.google.common.collect.ImmutableList;
import com.koloboke.collect.map.hash.HashObjObjMaps;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.ULong;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
import org.eclipse.milo.opcua.stack.core.types.structured.*;
import org.ini4j.Ini;
import org.ini4j.Wini;
import org.jinterop.dcom.core.JIUnsignedInteger;
import org.jinterop.dcom.core.JIUnsignedShort;

import java.io.File;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;

import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;

public class OpcUAService implements PluginService, Runnable {
    private Logger logger = ClientApplication.getLogger(OpcUAService.class);

    private static final String SERVICE_NAME = "OpcUaService";
    private final Map<String, Object> dataCache = HashObjObjMaps.newMutableMap();
    private Integer clientId = 0;
    private boolean status = false;
    private Wini ini = null;

    private OpcUAService(Integer clientId, Wini ini) {
        this.clientId = clientId;
        this.ini = ini;
    }

    public static PluginService getInstance(Integer clientId, Wini ini) {
        PluginService pluginService = OpcExecuterImpl.getPluginService(SERVICE_NAME + clientId);
        if (null == pluginService) {
            OpcUAService opcUaService = new OpcUAService(clientId, ini);
            OpcExecuterImpl.pluginServiceWorkerPool.submit(opcUaService);
            pluginService = OpcExecuterImpl.registPluginService(SERVICE_NAME + clientId, opcUaService);
        }
        return pluginService;
    }

    @Override
    public void run() {
        try {
            init();
        } catch (Exception e) {
            logger.debug(e.getMessage());
            OpcExecuterImpl.removePluginService(SERVICE_NAME + clientId);
            System.out.println("OPC UA client init fail.");
        }
    }

    private final String applicationName = "CodebanceOpcUaClient";
    private final String applicationUri = "urn:codebance:opcua:client";
    private String endpointUrl = "opc.tcp://192.168.32.159:49321";
    private SecurityPolicy securityPolicy = SecurityPolicy.None;
    private int strength = 2048;
    private String algorithm = "PKCS12";
    private int validity = 3;
    private int namespace = 2;
    private String user = "";
    private String password = "";

    private boolean onoff = true;
    private OpcUaClient client = null;
    private int reInitCount = 0;

    @Override
    public void init() throws Exception {
        try {
            if (ini != null && ini.containsKey("opc")) {
                Ini.Section section = ini.get("opc");
                if (section.containsKey("endpointUrl." + this.clientId)) {
                    endpointUrl = section.get("endpointUrl." + this.clientId);
                } else {
                    throw new Exception("parameter endpointUrl is empty.");
                }
                if (section.containsKey("securityPolicy." + this.clientId)) {
                    securityPolicy = BmSecurityPolicy.valueOf(Integer.parseInt(section.get("securityPolicy." + this.clientId)));
                } else {
                    throw new Exception("parameter securityPolicy is empty.");
                }
                if (section.containsKey("strength." + this.clientId)) {
                    strength = Integer.parseInt(section.get("strength." + this.clientId));
                }
                if (section.containsKey("algorithm." + this.clientId)) {
                    algorithm = section.get("algorithm." + this.clientId);
                }
                if (section.containsKey("validity." + this.clientId)) {
                    validity = Integer.parseInt(section.get("validity." + this.clientId));
                }
                if (section.containsKey("namespace." + this.clientId)) {
                    namespace = Integer.parseInt(section.get("namespace." + this.clientId));
                }
                if (section.containsKey("user." + this.clientId)) {
                    user = section.get("user." + this.clientId);
                }
                if (section.containsKey("password." + this.clientId)) {
                    password = section.get("password." + this.clientId);
                }
            } else {
                throw new Exception("opc_ua parameters is empty.");
            }
        } catch (ClassNotFoundException | SQLException e) {
            throw new Exception("opc_ua init fail.");
        }

        try {
            client = createClient();
            client.connect().whenComplete((c, ex) -> {
                if (ex == null) {
                    OpcUAService.this.status = true;
                }
            });

            while (onoff) {
                Thread.sleep(1000);
            }
        } catch (Throwable e) {
            logger.debug(e.getMessage());
            throw new Exception("opc_ua_service connect opc server fail.");
        } finally {
            if(client != null) {
                try {
                    client.disconnect();
                } catch (Exception e) {
                    logger.debug(e.getMessage());
                }
            }
            Thread.sleep(2000);
            onoff = true;
            System.out.println("opc client reinit...");
            if(reInitCount < 2) {
                reInitCount++;
                init();
            }
        }

    }

    /**
     * 创建OPC UA的服务连接对象
     */
    private OpcUaClient createClient() throws Exception {
        Path securityTempDir = new File(SystemUtil.getRuntimeDirPath(ClientApplication.class)).toPath();
        final KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir, strength, algorithm, validity, applicationName, applicationUri);

        // 创建OPC UA客户端
        return OpcUaClient.create(
                endpointUrl,
                (endpoints) -> {
                    Stream<EndpointDescription> stream = endpoints.stream();
                    Stream<EndpointDescription> endpointDescriptionStream = stream.filter(e -> e.getSecurityPolicyUri().equals(securityPolicy.getUri()));
                    return endpointDescriptionStream.findFirst();
                },
                configBuilder -> {
                    configBuilder.setApplicationName(LocalizedText.english(applicationName)).setApplicationUri(applicationUri);
                    if(securityPolicy != SecurityPolicy.None) {
                        configBuilder.setCertificate(loader.getClientCertificate())
                                .setCertificateChain(loader.getClientCertificateChain())
                                .setKeyPair(loader.getClientKeyPair());
                    }
                    if(!user.isEmpty() && !password.isEmpty()) {
                        configBuilder.setIdentityProvider(new UsernameProvider(user, password));
                    }else {
                        configBuilder.setIdentityProvider(new AnonymousProvider());
                    }
                    return configBuilder.setRequestTimeout(uint(5000)).build();
                }
        );
    }

    private void waiting(){
        long time = System.currentTimeMillis();
        while ((client == null || !status) && time - System.currentTimeMillis() <= 5000){
            try{
                Thread.sleep(100);
            }catch (Exception ex){}
            time = System.currentTimeMillis();
        }
    }

    /**
     * @MethodName: subscribe
     * @Description: 订阅节点变量
     * @throws Exception
     */
    public void subscribe(List<NodeId> nodes) throws Exception {
        waiting();
        if (client == null) {
            return;
        }

        // 查询订阅对象，没有则创建
        UaSubscription subscription = null;
        ImmutableList<UaSubscription> subscriptionList = client.getSubscriptionManager().getSubscriptions();
        if (CollectionUtils.isEmpty(subscriptionList)) {
            subscription = client.getSubscriptionManager().createSubscription(1000.0).get();
        } else {
            subscription = subscriptionList.get(0);
        }

        // 监控项请求列表
        List<MonitoredItemCreateRequest> requests = new ArrayList<>();

        if (!CollectionUtils.isEmpty(nodes)) {
            for (NodeId node : nodes) {
                if(!dataCache.containsKey(node.getIdentifier())) {
                    // 创建监控的参数
                    MonitoringParameters parameters = new MonitoringParameters(subscription.nextClientHandle(), 1000.0, // sampling
                            // interval
                            null, // filter, null means use default
                            uint(10), // queue size
                            true // discard oldest
                    );
                    // 创建订阅的变量， 创建监控项请 求
                    MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(
                            new ReadValueId(node, AttributeId.Value.uid(), null, null), MonitoringMode.Reporting, parameters);
                    requests.add(request);
                }
            }
        }

        if(requests.size() > 0) {
            // 创建监控项，并且注册变量值改变时候的回调函数
            subscription.createMonitoredItems(TimestampsToReturn.Both, requests, (item, id) -> {
                item.setValueConsumer((i, v) -> {
                    String identifier = i.getReadValueId().getNodeId().getIdentifier().toString();
                    Object value = parseValue(v.getValue());
                    dataCache.put(identifier, value);
                });
            }).get();
        }
    }

    @Override
    public PluginResultEntity execute(JSONObject jo){
        waiting();
        PluginResultEntity endResult = new PluginResultEntity();
        JSONObject outResult = new JSONObject();
        try {
            List<? extends UaNode> uaNodes = client.getAddressSpace().browseNodes(Identifiers.ObjectsFolder);
            for (UaNode node : uaNodes) {
                brower(node, outResult);
            }
        }catch (Exception e){
            logger.debug(e.getMessage());
        }
        endResult.setContext(outResult);
        endResult.setOk(true);
        return endResult;
    }

    /**
     * 递归遍历所有节点
     * @param node
     * @param ja
     */
    private void brower(UaNode node, JSONObject ja){
        try {
            List<? extends UaNode> uaNodes = node.browseNodes();
            if(uaNodes.size() > 0) {
                JSONObject sjo = new JSONObject();
                for (UaNode n : uaNodes) {
                    brower(n, sjo);
                }
                ja.put(node.getNodeId().getIdentifier().toString(), sjo);
            }
        }catch (Exception e){
            logger.debug(e.getMessage());
        }
    }

    @Override
    public PluginResultEntity execute(JSONArray inJsonArray) {
        waiting();
        PluginResultEntity endResult = new PluginResultEntity();
        JSONArray outResult = new JSONArray();
        if(client != null) {
            List<JSONObject> readArray = new ArrayList<>();
            List<JSONObject> writeArray = new ArrayList<>();
            for (int i = 0; i < inJsonArray.size(); i++) {
                String tag;
                int ns = namespace;
                boolean isWrite = false;
                Object v = null;
                JSONObject jo = null;
                try {
                    jo = inJsonArray.getJSONObject(i);
                } catch (Exception e) {
                }
                if (jo == null) {
                    tag = inJsonArray.getString(i);
                } else {
                    tag = jo.getString("tag");
                    if (jo.containsKey("ns")) {
                        ns = jo.getInteger("ns");
                    }
                    if(jo.containsKey("isWrite")){
                        isWrite = jo.getBoolean("isWrite");
                        if(isWrite){
                            v = jo.get("value");
                        }
                    }
                }
                Object ext = null;
                try{
                    ext = jo.get("ext");
                }catch (Exception ex){}

                if(isWrite){
                    JSONObject tmpJo = new JSONObject();
                    tmpJo.put("tag", tag);
                    tmpJo.put("ns", ns);
                    tmpJo.put("ext", ext);
                    tmpJo.put("value", v);
                    writeArray.add(tmpJo);
                }else{
                    JSONObject tmpJo = new JSONObject();
                    tmpJo.put("tag", tag);
                    tmpJo.put("ns", ns);
                    tmpJo.put("ext", ext);
                    readArray.add(tmpJo);
                }
            }
            if(readArray.size() > 0){
                JSONArray surplusReadArray = new JSONArray();
                for (JSONObject jo : readArray) {
                    if(dataCache.containsKey(jo.getString("tag"))) {
                        dumpCacheValue(jo.getString("tag"), dataCache.get(jo.getString("tag")), jo.get("ext"), outResult);
                    }else{
                        surplusReadArray.add(jo);
                    }
                }
                if(surplusReadArray.size() > 0) {
                    reads(surplusReadArray, outResult);
                }
            }
            if(writeArray.size() > 0){
                writes(writeArray, outResult);
            }
            endResult.setOk(true);
        }else{
            for (int i = 0; i < inJsonArray.size(); i++) {
                JSONObject jsonObject = new JSONObject();
                String itemName;
                JSONObject jo = null;
                try {
                    jo = inJsonArray.getJSONObject(i);
                } catch (Exception e) { }
                if (jo == null) {
                    itemName = inJsonArray.getString(i);
                } else {
                    itemName = jo.getString("tag");
                }
                Object ext = null;
                try{
                    ext = jo.get("ext");
                }catch (Exception ex){}
                createNullObject(jsonObject, itemName, "OpcUa client no connection created.", ext);
                outResult.add(jsonObject);
            }
            endResult.setOk(false);
            restart();
        }
        endResult.setContext(outResult);
        return endResult;
    }

    private void createNullObject(JSONObject jsonObject, String itemName, String remarks, Object ext){
        jsonObject.put("id", itemName);
        jsonObject.put("type", null);
        jsonObject.put("value", null);
        jsonObject.put("remarks", remarks);
        jsonObject.put("ext", ext);
    }

    /**
     * 批量读取
     * @param readArray
     * @param outResult
     */
    private void reads(List<JSONObject> readArray, JSONArray outResult){
        try {
            List<String> tags = new ArrayList<>();
            Map<String, Object> exts = new HashMap<>();
            final List<NodeId> items = new ArrayList<>();
            for (JSONObject jo : readArray) {
                tags.add(jo.getString("tag"));
                exts.put(jo.getString("tag"), jo.get("ext"));
                items.add(new NodeId(jo.getInteger("ns"), jo.getString("tag")));
            }
            List<DataValue> dataValues = client.readValues(0.0, TimestampsToReturn.Both, items).get();
            for (int i = 0;i < dataValues.size();i++){
                DataValue dv = dataValues.get(i);
                String tag = tags.get(i);
                JSONObject jo = new JSONObject();
                dumpValue(tag, dv, jo, exts.get(tag));
                outResult.add(jo);
                if(dv.getStatusCode() != StatusCode.GOOD) {
                    items.remove(items.get(i));
                }
            }
            ClientApplication.syncWorkerPool.submit(() -> {
                try {
                    subscribe(items);
                }catch (Exception e){
                    logger.debug(e.getMessage());
                }
            });
        }catch (Exception ex){
            for (JSONObject jo : readArray){
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("id", jo.getString("tag"));
                jsonObject.put("type", null);
                jsonObject.put("value", null);
                jsonObject.put("ext", jo.get("ext"));
                jsonObject.put("good", false);
                jsonObject.put("remarks", ex.getMessage());
                outResult.add(jsonObject);
            }
        }
    }

    /**
     * 批量写入
     * @param writeArray
     * @param outResult
     */
    private void writes(List<JSONObject> writeArray, JSONArray outResult){
        try {
            List<NodeId> items = new ArrayList<>();
            List<String> tags = new ArrayList<>();
            Map<String, Object> exts = new HashMap<>();
            Map<String, Object> values = new HashMap<>();
            List<NodeId> nodeIds = new ArrayList<>();
            List<DataValue> dvs = new ArrayList<>();
            for (JSONObject jo : writeArray) {
                items.add(new NodeId(jo.getInteger("ns"), jo.getString("tag")));
                tags.add(jo.getString("tag"));
                values.put(jo.getString("tag"), jo.get("value"));
                exts.put(jo.getString("tag"), jo.get("ext"));
                nodeIds.add(new NodeId(jo.getInteger("ns"), jo.getString("tag")));
//                dvs.add(new DataValue(new Variant(jo.get("value")), null, null, null));
            }
            List<DataValue> dataValues = client.readValues(0.0, TimestampsToReturn.Both, items).get();
            for (int i = 0;i < dataValues.size();i++) {
                DataValue dv = dataValues.get(i);
                String s = dv.getValue().getDataType().get().getIdentifier().toString();
                String t = tags.get(i);
                //类型定义在：org.eclipse.milo.opcua.stack.core.Identifiers
                if(s.equals("1")){
                    dvs.add(new DataValue(new Variant(Boolean.valueOf(values.get(t).toString())), null, null, null));
                }else if(s.equals("3")){
                    dvs.add(new DataValue(new Variant(Byte.valueOf(values.get(t).toString())), null, null, null));
                }else if(s.equals("4")){
                    dvs.add(new DataValue(new Variant(Short.valueOf(values.get(t).toString())), null, null, null));
                }else if(s.equals("6") || s.equals("27")){
                    dvs.add(new DataValue(new Variant(Integer.valueOf(values.get(t).toString())), null, null, null));
                }else if(s.equals("8") || s.equals("26")){
                    dvs.add(new DataValue(new Variant(Long.valueOf(values.get(t).toString())), null, null, null));
                }else if(s.equals("10")){
                    dvs.add(new DataValue(new Variant(Float.valueOf(values.get(t).toString())), null, null, null));
                }else if(s.equals("11")){
                    dvs.add(new DataValue(new Variant(Double.valueOf(values.get(t).toString())), null, null, null));
                }else if(s.equals("12")){
                    dvs.add(new DataValue(new Variant(values.get(t).toString()), null, null, null));
                }else if(s.equals("13")){
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    Date d = sdf.parse(values.get(t).toString());
                    DateTime dt = new DateTime(d);
                    dvs.add(new DataValue(new Variant(dt), null, null, null));
                }else if(s.equals("292")){
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    Date d = sdf.parse(values.get(t).toString());
                    dvs.add(new DataValue(new Variant(d.getTime()), null, null, null));
                }else if(s.equals("293")){
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    dvs.add(new DataValue(new Variant(sdf.parse(values.get(t).toString())), null, null, null));
                }else if(s.equals("294")){
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    Date d = sdf.parse(values.get(t).toString());
                    DateTime dt = new DateTime(d);
                    dvs.add(new DataValue(new Variant(dt.getUtcTime()), null, null, null));
                }else{
                    dvs.add(new DataValue(new Variant(values.get(t)), null, null, null));
                }

            }
            List<StatusCode> statusCodes = client.writeValues(nodeIds, dvs).get();
            for (int i = 0;i < statusCodes.size();i++){
                StatusCode sc = statusCodes.get(i);
                String tag = tags.get(i);
                JSONObject jo = new JSONObject();
                getValue(tag, sc, values.get(tag), jo, exts.get(tag));
                outResult.add(jo);
            }
        }catch (Exception ex){
            ex.printStackTrace();
            for (JSONObject jo : writeArray){
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("id", jo.getString("tag"));
                jsonObject.put("type", null);
                jsonObject.put("value", null);
                jsonObject.put("ext", jo.get("ext"));
                outResult.add(jo);
            }
        }
    }

    /**
     * 单个读取
     * @param nodeId
     * @param jo
     * @param ext
     * @throws Exception
     */
    private void read(NodeId nodeId, JSONObject jo, Object ext) throws Exception{
        DataValue value = client.readValue(0.0, TimestampsToReturn.Both, nodeId).get();
        if (value == null) {
            throw new Exception("Not found tag in ns or connect server fail");
        }
        dumpValue(nodeId.getIdentifier().toString(), value, jo, ext);
    }

    /**
     * 单个写入
     * @param nodeId
     * @param value
     * @param jsonObject
     * @param ext
     * @throws ExecutionException
     * @throws InterruptedException
     */
    private void write(NodeId nodeId, Object value, JSONObject jsonObject, Object ext) throws ExecutionException, InterruptedException {
        if(value instanceof BigDecimal){
            value = ((BigDecimal) value).doubleValue();
        }
        Variant var = new Variant(value);
        DataValue dv = new DataValue(var, null, null, null);
        CompletableFuture<StatusCode> statusCodeCompletableFuture = client.writeValue(nodeId, dv);
        StatusCode statusCode = statusCodeCompletableFuture.get();
        getValue(nodeId.getIdentifier().toString(), statusCode, value, jsonObject, ext);
    }

    /**
     * 获取值
     * @param id
     * @param statusCode
     * @param value
     * @param jsonObject
     * @param ext
     */
    private void getValue(String id, StatusCode statusCode, Object value, JSONObject jsonObject, Object ext){
        jsonObject.put("id", id);
        if(statusCode.isGood()){
            jsonObject.put("type", value.getClass().getName());
            jsonObject.put("value", value);
            jsonObject.put("good", true);
        }else{
            jsonObject.put("type", value.getClass().getName());
            jsonObject.put("value", null);
            jsonObject.put("good", false);
            logger.debug(id + ": " + statusCode.toString());
        }
        jsonObject.put("ext", ext);
    }

    /**
     * 设置值
     * @param id
     * @param dv
     * @param jo
     * @param ext
     */
    private void dumpValue(String id, DataValue dv, JSONObject jo, Object ext){
        jo.put("id", id);
        Variant vO = dv.getValue();
        Object ev = vO.getValue();
        if (ev instanceof Boolean) {
            jo.put("type", Boolean.class.getName());
            jo.put("value", Boolean.valueOf(ev.toString()));
        } else if (ev instanceof Long || ev instanceof Integer || ev instanceof Short || ev instanceof Byte) {
            jo.put("type", Long.class.getName());
            jo.put("value", Long.valueOf(ev.toString()));
        } else if (ev instanceof Double || ev instanceof Float) {
            jo.put("type", Double.class.getName());
            jo.put("value", Double.valueOf(ev.toString()));
        }else if (ev instanceof JIUnsignedShort) {
            JIUnsignedShort vl = (JIUnsignedShort) ev;
            jo.put("type", Long.class.getName());
            jo.put("value", Long.valueOf(vl.getValue().toString()));
        }else if (ev instanceof UShort) {
            UShort vl = (UShort) ev;
            jo.put("type", Long.class.getName());
            jo.put("value", vl.longValue());
        }else if (ev instanceof JIUnsignedInteger) {
            JIUnsignedInteger vi = (JIUnsignedInteger) ev;
            jo.put("type", Integer.class.getName());
            jo.put("value", Integer.valueOf(vi.getValue().toString()));
        }else if (ev instanceof UInteger) {
            UInteger uev = (UInteger) ev;
            jo.put("type", Integer.class.getName());
            jo.put("value", uev.intValue());
        }else if (ev instanceof ULong) {
            ULong uvl = (ULong) ev;
            jo.put("type", Long.class.getName());
            jo.put("value", uvl.longValue());
        }else if (ev instanceof Date) {
            jo.put("type", Date.class.getName());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            jo.put("value", sdf.format(ev));
        }else if (ev instanceof DateTime) {
            DateTime dt = (DateTime)ev;
            jo.put("type", Date.class.getName());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            jo.put("value", sdf.format(dt.getJavaDate()));
        } else {
            jo.put("type", String.class.getName());
            jo.put("value", String.valueOf(ev));
        }
        jo.put("good", dv.getStatusCode().isGood());
        jo.put("ext", ext);
    }

    /**
     * 从缓存读数据
     * @param id
     * @param ev
     * @param ext
     * @param outResult
     */
    private void dumpCacheValue(String id, Object ev, Object ext, JSONArray outResult){
        JSONObject jo = new JSONObject();
        jo.put("id", id);
        if (ev instanceof Boolean) {
            jo.put("type", Boolean.class.getName());
            jo.put("value", Boolean.valueOf(ev.toString()));
        } else if (ev instanceof Long || ev instanceof Integer || ev instanceof Short || ev instanceof Byte) {
            jo.put("type", Long.class.getName());
            jo.put("value", Long.valueOf(ev.toString()));
        } else if (ev instanceof Double || ev instanceof Float) {
            jo.put("type", Double.class.getName());
            jo.put("value", Double.valueOf(ev.toString()));
        }else if (ev instanceof JIUnsignedShort) {
            JIUnsignedShort vl = (JIUnsignedShort) ev;
            jo.put("type", Long.class.getName());
            jo.put("value", Long.valueOf(vl.getValue().toString()));
        }else if (ev instanceof UShort) {
            UShort vl = (UShort) ev;
            jo.put("type", Long.class.getName());
            jo.put("value", vl.longValue());
        }else if (ev instanceof JIUnsignedInteger) {
            JIUnsignedInteger vi = (JIUnsignedInteger) ev;
            jo.put("type", Integer.class.getName());
            jo.put("value", Integer.valueOf(vi.getValue().toString()));
        }else if (ev instanceof UInteger) {
            UInteger uev = (UInteger) ev;
            jo.put("type", Integer.class.getName());
            jo.put("value", uev.intValue());
        }else if (ev instanceof ULong) {
            ULong uvl = (ULong) ev;
            jo.put("type", Long.class.getName());
            jo.put("value", uvl.longValue());
        }else if (ev instanceof Date) {
            jo.put("type", Date.class.getName());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            jo.put("value", sdf.format(ev));
        }else if (ev instanceof DateTime) {
            DateTime dt = (DateTime)ev;
            jo.put("type", Date.class.getName());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            jo.put("value", sdf.format(dt.getJavaDate()));
        } else {
            jo.put("type", String.class.getName());
            jo.put("value", String.valueOf(ev));
        }
        jo.put("good", true);
        jo.put("ext", ext);
        outResult.add(jo);
    }

    /**
     *解析值
     * @param vO
     * @return
     */
    private Object parseValue(Variant vO){
        Object ev = vO.getValue();
        if (ev instanceof Boolean) {
            return Boolean.valueOf(ev.toString());
        } else if (ev instanceof Long || ev instanceof Integer || ev instanceof Short || ev instanceof Byte) {
            return Long.valueOf(ev.toString());
        } else if (ev instanceof Double || ev instanceof Float) {
            return Double.valueOf(ev.toString());
        }else if (ev instanceof JIUnsignedShort) {
            JIUnsignedShort vl = (JIUnsignedShort) ev;
            return Long.valueOf(vl.getValue().toString());
        }else if (ev instanceof UShort) {
            UShort vl = (UShort) ev;
            return vl.longValue();
        }else if (ev instanceof JIUnsignedInteger) {
            JIUnsignedInteger vi = (JIUnsignedInteger) ev;
            return Integer.valueOf(vi.getValue().toString());
        }else if (ev instanceof UInteger) {
            UInteger uev = (UInteger) ev;
            return uev.intValue();
        }else if (ev instanceof ULong) {
            ULong uvl = (ULong) ev;
            return uvl.longValue();
        }else if (ev instanceof Date) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return sdf.format(ev);
        }else if (ev instanceof DateTime) {
            DateTime dt = (DateTime)ev;
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return sdf.format(dt.getJavaDate());
        } else {
            return String.valueOf(ev);
        }
    }

    /**
     * 重新连接
     */
    private void restart(){
        onoff = false;
        OpcExecuterImpl.removePluginService(SERVICE_NAME + clientId);
    }
}

enum BmSecurityPolicy {
    None(0), Basic128Rsa15(1), Basic256(2), Basic256Sha256(3), Aes128_Sha256_RsaOaep(4), Aes256_Sha256_RsaPss(5);
    private SecurityPolicy sp;
    private int key;

    BmSecurityPolicy(int key) {
        this.key = key;
        switch (key) {
            case 1:
                this.sp = SecurityPolicy.Basic128Rsa15;
                break;
            case 2:
                this.sp = SecurityPolicy.Basic256;
                break;
            case 3:
                this.sp = SecurityPolicy.Basic256Sha256;
                break;
            case 4:
                this.sp = SecurityPolicy.Aes128_Sha256_RsaOaep;
                break;
            case 5:
                this.sp = SecurityPolicy.Aes256_Sha256_RsaPss;
                break;
            case 0:
            default:
                this.sp = SecurityPolicy.None;
                break;
        }
    }

    public static SecurityPolicy valueOf(int key){
        BmSecurityPolicy[] values = values();
        for (BmSecurityPolicy bp : values){
            if(bp.key == key){
                return bp.sp;
            }
        }
        return null;
    }
}